/**
 * 挖宝整体流程自测脚本（2000/1591/1412/1411/1592）
 * 流程：HTTP登录 → WS连接 → 选择角色 → 进入密室(2000) → 查询坑位(1591)
 *      若不足 → 购买洛阳铲(1412) → 再查1591 → 连续挖9坑(1411) → 1592查看动态
 */
const http = require('http');
const WebSocket = require('ws');

const CONFIG = {
    host: process.env.WS_HOST || 'localhost',
    port: parseInt(process.env.WS_PORT || process.env.HTTP_PORT || '3000', 10),
    mobile: process.env.TEST_USER_MOBILE || '17091279999',
    pwdHash: process.env.TEST_USER_PASSWORD_HASH || 'e10adc3949ba59abbe56e057f20f883e',
};

async function main() {
    const token = await loginOrRegister();
    const ws = await connectWs(token);
    await delay(400);
    await ensureCharacter(ws);
    await send(ws, { c: 2000, d: { id: 600 } }, 300);

    // 0) 摆摊全流程快速验证
    await testStallFlow(ws);

    // 1) 查询坑位并校验结构（保留原挖宝流程）
    let q = await sendAndWait(ws, 1591, {});
    if (q.code !== 0) throw new Error('1591失败: ' + (q.msg || ''));
    validatePits(q);

    // 2) 循环挖掘，自动购买次数，直至本页完成或命中上限
    let prevTimesLeft = Number.MAX_SAFE_INTEGER;
    let digs = 0;
    while (true) {
        const pitsNow = Array.isArray(q.data) ? q.data : [];
        const nextZero = pitsNow.find((it) => Array.isArray(it) && it[0] === 0);
        if (!nextZero) break;
        const pitId = nextZero[1];
        const r = await digWithAutoBuy(ws, pitId);
        if (r.success) {
            const timesLeft = Number(r.msg?.data?.[2]);
            if (Number.isFinite(timesLeft)) {
                if (prevTimesLeft !== Number.MAX_SAFE_INTEGER && !(timesLeft < prevTimesLeft)) {
                    console.warn('⚠️ timesLeft 未递减: prev=', prevTimesLeft, ' now=', timesLeft);
                }
                prevTimesLeft = timesLeft;
            }
            digs++;
        } else if (r.hitDailyCap) {
            console.log('⛔ 达到每日上限，停止挖掘');
            break;
        } else {
            throw new Error('1411失败: ' + (r.msg?.msg || r.msg));
        }
        await delay(80);
        q = await sendAndWait(ws, 1591, {});
        if (q.code !== 0) throw new Error('1591失败: ' + (q.msg || ''));
    }

    // 3) 挖宝矿石入包校验：对比 6008 前后数量
    try {
        const shop = await sendAndWait(ws, 1265, { t: 2, pageNum: 0 });
        const pickFrom = (arr, kw) => arr.find((x) => typeof x?.name === 'string' && x.name.includes(kw));
        const chosen = (Array.isArray(shop?.list) && (pickFrom(shop.list, '1级铁矿') || pickFrom(shop.list, '1级银矿') || pickFrom(shop.list, '1级金矿') || shop.list[0])) || null;
        if (chosen) {
            const mineralId = Number(chosen.id);
            const before = await sendAndWait(ws, 6008, { pageNum: 0 });
            const beforeNum = getMineralCount(before, mineralId);
            // 再次尝试挖若干次，尽量命中矿石
            for (let k = 0; k < 10; k++) {
                const qTry = await sendAndWait(ws, 1591, {});
                const pits = Array.isArray(qTry.data) ? qTry.data : [];
                const zero = pits.find((it) => Array.isArray(it) && it[0] === 0);
                if (!zero) break;
                const r = await digWithAutoBuy(ws, zero[1]);
                if (!r.success) break;
            }
            const after = await sendAndWait(ws, 6008, { pageNum: 0 });
            const afterNum = getMineralCount(after, mineralId);
            if (afterNum < beforeNum) throw new Error('6008 数量异常减少');
            if (afterNum === beforeNum) console.warn('⚠️ 本轮未挖到矿石，跳过断言');
            else console.log('✅ 挖宝后矿石入包生效: +', (afterNum - beforeNum));
        }
    } catch (e) {
        console.warn('⚠️ 矿石入包校验警告:', e.message);
    }

    // 4) 再次校验坑位结构
    const q2 = await sendAndWait(ws, 1591, {});
    if (q2.code !== 0) throw new Error('二次1591失败: ' + (q2.msg || ''));
    validatePits(q2);

    // 5) 中奖动态
    const hist = await sendAndWait(ws, 1592, {});
    const histCount = Array.isArray(hist.data) ? hist.data.length : 0;
    if (digs > 0 && histCount < 1) console.warn('⚠️ 中奖动态为空，但本次有挖掘。');
    console.log('✅ 挖掘次数:', digs, ' | 中奖动态条数:', histCount);
    console.log('🎉 挖宝整套脚本测试完成');
    try { ws.close(); } catch (_) { }
}

function loginOrRegister() {
    return new Promise((resolve, reject) => {
        const req = http.request({
            hostname: CONFIG.host,
            port: CONFIG.port,
            path: '/user-service/user/login',
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
        }, (res) => {
            let data = '';
            res.on('data', (chunk) => (data += chunk));
            res.on('end', () => {
                try {
                    const json = JSON.parse(data || '{}');
                    if (json.code === 0 && json.data?.accessToken) return resolve(json.data.accessToken);
                    // 尝试注册后再登录
                    registerUser()
                        .then(() => loginOnly().then(resolve).catch(reject))
                        .catch(() => loginOnly().then(resolve).catch(reject));
                } catch (e) {
                    reject(new Error('登录响应解析失败'));
                }
            });
        });
        req.on('error', reject);
        req.write(JSON.stringify({ mobile: CONFIG.mobile, pwd: CONFIG.pwdHash }));
        req.end();
    });
}

function loginOnly() {
    return new Promise((resolve, reject) => {
        const req = http.request({
            hostname: CONFIG.host,
            port: CONFIG.port,
            path: '/user-service/user/login',
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
        }, (res) => {
            let data = '';
            res.on('data', (chunk) => (data += chunk));
            res.on('end', () => {
                try {
                    const json = JSON.parse(data || '{}');
                    if (json.code === 0 && json.data?.accessToken) resolve(json.data.accessToken);
                    else reject(new Error('登录失败: ' + (json.msg || '')));
                } catch (e) { reject(new Error('登录响应解析失败')); }
            });
        });
        req.on('error', reject);
        req.write(JSON.stringify({ mobile: CONFIG.mobile, pwd: CONFIG.pwdHash }));
        req.end();
    });
}

function registerUser() {
    return new Promise((resolve) => {
        const req = http.request({
            hostname: CONFIG.host,
            port: CONFIG.port,
            path: '/user-service/user/register',
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
        }, (res) => {
            let data = '';
            res.on('data', (chunk) => (data += chunk));
            res.on('end', () => {
                try {
                    const json = JSON.parse(data || '{}');
                    if (json.code === 0) return resolve(true);
                } catch (_) { }
                resolve(true); // 已存在等情况也继续
            });
        });
        req.on('error', () => resolve(true));
        req.write(JSON.stringify({ mobile: CONFIG.mobile, pwd: CONFIG.pwdHash, captcha: '123456' }));
        req.end();
    });
}

function connectWs(token) {
    return new Promise((resolve, reject) => {
        const ws = new WebSocket(`ws://${CONFIG.host}:${CONFIG.port}/user-service/websocket?t=${token}`);
        ws.on('open', () => resolve(ws));
        ws.on('error', reject);
        setTimeout(() => reject(new Error('WS连接超时')), 10000);
    });
}

async function ensureCharacter(ws) {
    const list = await waitAct(ws, 3, () => ws.send(JSON.stringify({ c: 3, d: null })));
    const role = Array.isArray(list.roleList) && list.roleList[0];
    if (!role) {
        await waitAct(ws, 5, () => ws.send(JSON.stringify({ c: 5, d: { name: '挖宝测试', sex: 1, roleType: 1, headIndex: 2 } })), 15000);
        await delay(800);
    }
    const list2 = await waitAct(ws, 3, () => ws.send(JSON.stringify({ c: 3, d: null })));
    const role2 = Array.isArray(list2.roleList) && list2.roleList[0];
    if (!role2) throw new Error('无可用角色');
    await waitAct(ws, 4, () => ws.send(JSON.stringify({ c: 4, d: { characterId: role2.id } })));
}

async function testStallFlow(ws) {
    // 查询我的摊位
    const mine1 = await sendAndWait(ws, 1311, {});
    if (mine1.code === 8) {
        // 创建角色名后自动创建摊位由后端 getOrCreate 支持
    }

    // 改名
    await sendAndWait(ws, 1315, { name: '我的摊名字' }).catch(() => ({}));
    await delay(50);

    // 再查
    const mine2 = await sendAndWait(ws, 1311, {});
    // 上架体力丹(82) 4 个，pos=0，单价 33333（若背包不足自动跳过）
    try { await sendAndWait(ws, 1325, { id: 82, pos: 0, count: 4, money: 33333 }); } catch (_) { }
    await delay(50);

    // 发布（普通发布 money=0）
    await sendAndWait(ws, 1318, { money: 0 });
    await delay(50);

    // 分类刷新 type=1（药品）
    await sendAndWait(ws, 1264, { type: 1, pageNum: 0 });

    // 查询自己摊位信息（打包状态示例略）
    await sendAndWait(ws, 1032, { stallId: mine2?.data?.role_id || mine2?.data?.roleId || 0 }).catch(() => ({}));

    // 查看商品详情
    await sendAndWait(ws, 1312, { gid: 9 }).catch(() => ({}));

    // 查看出售记录
    const sid = mine2?.data?.role_id || mine2?.data?.roleId || 0;
    if (sid) await sendAndWait(ws, 1308, { stallId: sid }).catch(() => ({}));

    // 撤销摊位（退货）
    await sendAndWait(ws, 1307, {});
}

function send(ws, obj, delayMs = 0) { ws.send(JSON.stringify(obj)); return delay(delayMs); }

function sendAndWait(ws, actId, d) { return waitAct(ws, actId, () => ws.send(JSON.stringify({ c: actId, d: Object.keys(d || {}).length ? d : null }))); }

function waitAct(ws, actId, sendFn, timeoutMs = 10000) {
    return new Promise((resolve, reject) => {
        const handler = (raw) => {
            try {
                const msg = JSON.parse(raw.toString());
                if (msg.act_id === actId) { ws.off('message', handler); clearTimeout(timer); resolve(msg); }
            } catch (_) { }
        };
        const timer = setTimeout(() => { ws.off('message', handler); reject(new Error(`等待 ${actId} 超时`)); }, timeoutMs);
        ws.on('message', handler);
        try { sendFn(); } catch (e) { ws.off('message', handler); clearTimeout(timer); reject(e); }
    });
}

function delay(ms) { return new Promise((r) => setTimeout(r, ms)); }

async function digWithAutoBuy(ws, pitId) {
    let attempts = 0;
    while (attempts < 3) {
        attempts++;
        const r = await sendAndWait(ws, 1411, { i: pitId });
        if (r.code === 0) return { success: true, msg: r };
        if (r.code === 8) {
            const txt = (r.msg || '').toString();
            if (txt.includes('上限')) return { success: false, hitDailyCap: true, msg: r };
            if (txt.includes('不足')) {
                const buy = await sendAndWait(ws, 1412, { c: 1 });
                if (buy.code !== 0) return { success: false, msg: buy };
                await delay(50);
                continue;
            }
        }
        return { success: false, msg: r };
    }
    return { success: false, msg: '重试次数过多' };
}

function validatePits(resp) {
    const pits = Array.isArray(resp.data) ? resp.data : [];
    if (pits.length !== 9) console.warn('⚠️ 1591返回坑位数量非9:', pits.length);
    for (const it of pits) {
        if (!Array.isArray(it) || it.length !== 2) throw new Error('1591坑位项结构错误');
        const [state, pitId] = it;
        if (!Number.isFinite(Number(state)) || !Number.isFinite(Number(pitId))) {
            throw new Error('1591坑位项类型错误');
        }
    }
}

function getMineralCount(resp, id) {
    const list = Array.isArray(resp?.list) ? resp.list : [];
    const row = list.find((x) => Number(x?.id) === Number(id));
    return Number(row?.num || 0);
}

if (require.main === module) {
    main().catch((e) => { console.error('❌ 自测失败:', e.message); process.exit(1); });
}


